#ifndef RAYTRACEPRO_RENDERER_H
#define RAYTRACEPRO_RENDERER_H
#include "RayTraceSystem.h"
#include "RenderBuffer.h"
#include "CameraProjection.h"
#include "Ray.h"
#include "DeviceList.h"
#include "Scene.h"
#include "CL/cl.hpp"
#include <ppl.h>
#include "Basic/FileSystem.h"
#include "Windows/GxWindows.h"
#include "RenderContext.h"

namespace RayTracePro
{
	using namespace Concurrency;

	class Scene;
	class Hilbert;
	struct RenderTask
	{
		int X, Y, Width, Height;
	};
	struct RenderStatistics
	{
		int TotalBlocks;
		int AverageBlockSize;
		int BlocksCompletedByCpu;
		double TimeElapsedInSeconds;
		RenderStatistics()
		{
			TotalBlocks = 0; 
			AverageBlockSize = 0;
			BlocksCompletedByCpu = 0;
			TimeElapsedInSeconds = 0.0;
		}
	};
	
	class Renderer : public GxLibBasic::Object
	{
	private:
		RayTraceSystem * system;
		RenderBuffer * buffer;
		Scene * scene;
		Hilbert * hilbert;
		cl::Kernel kernelComputeCameraRays, kernelDebugShade;
		Array<RenderTask, true> tasks;
		LARGE_INTEGER startTime;
		long volatile taskPtr;
		long volatile completedTasks;
		int taskCompletedByCPU;
		bool cancelled;
		RenderContext cpuContext, gpuContext;
		RefPtr<GxWinSystem::Threading::gxThread> cpuThread, gpuThread;
		void CpuRenderThread();
		void GpuRenderThread();
		void DebugTestShade(bool gpu, DeviceList<Vec4> & colors, DeviceList<DifferentialGeometry> & intersections);
	public:
		CameraProjection Camera;
		Renderer(RayTraceSystem * system);
		~Renderer();
	public:
		GxLibBasic::Event<const RenderTask &> BlockFinished;
		GxLibBasic::Event<bool, const RenderStatistics &> RenderFinished;
	private:
		inline void OnBlockFinished(const RenderTask & task)
		{
			BlockFinished.Invoke(task);
			if (completedTasks == tasks.Count())
			{
				RenderStatistics stats;
				stats.AverageBlockSize = tasks[0].Width;
				stats.BlocksCompletedByCpu = taskCompletedByCPU;
				LARGE_INTEGER freq, endTime;
				QueryPerformanceFrequency(&freq);
				QueryPerformanceCounter(&endTime);
				stats.TimeElapsedInSeconds = ((endTime.QuadPart-startTime.QuadPart)/(double)freq.QuadPart); 
				stats.TotalBlocks = tasks.Count();
				RenderFinished.Invoke(true, stats);
			}
		}
		void DefaultShade(bool GPU, DeviceList<float> & colors, DeviceList<Ray> & rays, DeviceList<RayDifferential> & rayDiffs, DeviceList<int> & rayIds, int start, int end, RenderContext & context);
		void Shade(bool GPU,
			DeviceList<float> & colors,
			DeviceList<DifferentialGeometry> & intersections, 
			DeviceList<Ray> & rays, 
			DeviceList<RayDifferential> & rayDiffs, 
			RenderContext & context);
	public:
		void SetScene(Scene * scene);
		void SetScreenBuffer(RenderBuffer * buffer);
		
		void RenderPixel(int x, int y)
		{
			Ray ray;
			RayDifferential rayDiff;
			CameraProjection::GenerateRay(Camera, ray, rayDiff, (float)x, (float)y);
			printf("Ray.Origin: (%f, %f, %f)\n", ray.Origin.x, ray.Origin.y, ray.Origin.z);
			printf("Ray.Direction: (%f, %f, %f)\n", ray.Direction.x, ray.Direction.y, ray.Direction.z);
		}

		void RenderBlock(bool GPU, int x0, int y0, int width, int height, RenderContext & context);
		
		enum RenderDevice
		{
			CPU, GPU, Both
		};
		bool RenderFrameAsync(RenderDevice device = Both);
		void Cancel();
	};
}

#endif